"""EDA数据输入输出工具

提供EDA数据的加载和保存功能，支持：
- 多种文件格式（CSV、JSON、Excel、TXT）
- 数据格式自动检测和转换
- 元数据处理
- 错误处理和数据验证

主要函数：
- load_eda_data: 加载EDA数据
- save_eda_results: 保存EDA处理结果
- detect_file_format: 检测文件格式
- validate_eda_data: 验证EDA数据
"""

import os
import json
import logging
from pathlib import Path
from typing import Dict, Any, Optional, Union, List, Tuple
import pandas as pd
import numpy as np
from datetime import datetime
import warnings

logger = logging.getLogger(__name__)

def load_eda_data(file_path: Union[str, Path], 
                 encoding: str = 'utf-8',
                 **kwargs) -> Optional[Union[Dict[str, Any], np.ndarray]]:
    """加载EDA数据文件
    
    支持多种文件格式：CSV、JSON、Excel、TXT
    
    Args:
        file_path: 文件路径
        encoding: 文件编码（默认utf-8）
        **kwargs: 额外的加载参数
        
    Returns:
        加载的EDA数据，格式为字典或numpy数组
        字典格式: {'signal': array, 'sampling_rate': float, 'metadata': dict}
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        logger.error(f"文件不存在: {file_path}")
        return None
    
    try:
        logger.debug(f"开始加载EDA数据: {file_path}")
        
        # 检测文件格式
        file_format = detect_file_format(file_path)
        logger.debug(f"检测到文件格式: {file_format}")
        
        # 根据格式加载数据
        if file_format == 'csv':
            data = _load_csv_data(file_path, encoding, **kwargs)
        elif file_format == 'json':
            data = _load_json_data(file_path, encoding, **kwargs)
        elif file_format == 'excel':
            data = _load_excel_data(file_path, **kwargs)
        elif file_format == 'txt':
            data = _load_txt_data(file_path, encoding, **kwargs)
        else:
            logger.error(f"不支持的文件格式: {file_format}")
            return None
        
        # 验证数据
        if data is not None:
            data = validate_eda_data(data)
        
        logger.debug(f"EDA数据加载成功: {file_path}")
        return data
        
    except Exception as e:
        logger.error(f"加载EDA数据失败 {file_path}: {str(e)}")
        return None

def _load_csv_data(file_path: Path, encoding: str, **kwargs) -> Optional[Dict[str, Any]]:
    """加载CSV格式的EDA数据"""
    try:
        # 尝试读取CSV文件
        df = pd.read_csv(file_path, encoding=encoding, **kwargs)
        
        # 检测数据列
        signal_column = None
        time_column = None
        
        # 常见的EDA信号列名
        eda_column_names = ['eda', 'gsr', 'conductance', 'signal', 'value', 'data']
        time_column_names = ['time', 'timestamp', 't', 'seconds']
        
        # 查找EDA信号列
        for col in df.columns:
            col_lower = col.lower()
            if any(name in col_lower for name in eda_column_names):
                signal_column = col
                break
        
        # 查找时间列
        for col in df.columns:
            col_lower = col.lower()
            if any(name in col_lower for name in time_column_names):
                time_column = col
                break
        
        # 如果没找到明确的信号列，使用第一个数值列
        if signal_column is None:
            numeric_columns = df.select_dtypes(include=[np.number]).columns
            if len(numeric_columns) > 0:
                signal_column = numeric_columns[0]
            else:
                logger.error("CSV文件中未找到数值列")
                return None
        
        # 提取信号数据
        eda_signal = df[signal_column].values
        
        # 计算采样率
        sampling_rate = 4.0  # 默认采样率
        if time_column is not None:
            time_data = df[time_column].values
            if len(time_data) > 1:
                time_diff = np.diff(time_data)
                avg_interval = np.mean(time_diff)
                if avg_interval > 0:
                    sampling_rate = 1.0 / avg_interval
        
        # 构建结果
        result = {
            'signal': eda_signal,
            'sampling_rate': sampling_rate,
            'metadata': {
                'file_path': str(file_path),
                'file_format': 'csv',
                'signal_column': signal_column,
                'time_column': time_column,
                'total_columns': len(df.columns),
                'data_shape': df.shape,
                'load_timestamp': datetime.now().isoformat()
            }
        }
        
        # 添加其他列作为附加数据
        additional_data = {}
        for col in df.columns:
            if col != signal_column and col != time_column:
                additional_data[col] = df[col].values
        
        if additional_data:
            result['additional_data'] = additional_data
        
        return result
        
    except Exception as e:
        logger.error(f"加载CSV文件失败: {str(e)}")
        return None

def _load_json_data(file_path: Path, encoding: str, **kwargs) -> Optional[Dict[str, Any]]:
    """加载JSON格式的EDA数据"""
    try:
        with open(file_path, 'r', encoding=encoding) as f:
            data = json.load(f)
        
        # 如果是字典格式，直接返回
        if isinstance(data, dict):
            # 确保有signal字段
            if 'signal' in data or 'data' in data or 'eda' in data:
                signal_key = 'signal' if 'signal' in data else ('data' if 'data' in data else 'eda')
                
                result = {
                    'signal': np.array(data[signal_key]),
                    'sampling_rate': data.get('sampling_rate', 4.0),
                    'metadata': {
                        'file_path': str(file_path),
                        'file_format': 'json',
                        'original_keys': list(data.keys()),
                        'load_timestamp': datetime.now().isoformat()
                    }
                }
                
                # 添加其他字段作为元数据
                for key, value in data.items():
                    if key not in ['signal', 'data', 'eda', 'sampling_rate']:
                        result['metadata'][key] = value
                
                return result
        
        # 如果是列表格式，假设为信号数据
        elif isinstance(data, list):
            return {
                'signal': np.array(data),
                'sampling_rate': 4.0,
                'metadata': {
                    'file_path': str(file_path),
                    'file_format': 'json',
                    'data_type': 'list',
                    'load_timestamp': datetime.now().isoformat()
                }
            }
        
        else:
            logger.error("JSON文件格式不支持")
            return None
            
    except Exception as e:
        logger.error(f"加载JSON文件失败: {str(e)}")
        return None

def _load_excel_data(file_path: Path, **kwargs) -> Optional[Dict[str, Any]]:
    """加载Excel格式的EDA数据"""
    try:
        # 读取Excel文件
        df = pd.read_excel(file_path, **kwargs)
        
        # 使用与CSV相同的逻辑处理数据
        return _process_dataframe_data(df, file_path, 'excel')
        
    except Exception as e:
        logger.error(f"加载Excel文件失败: {str(e)}")
        return None

def _load_txt_data(file_path: Path, encoding: str, **kwargs) -> Optional[Dict[str, Any]]:
    """加载TXT格式的EDA数据"""
    try:
        # 尝试不同的分隔符
        separators = ['\t', ',', ' ', ';']
        
        for sep in separators:
            try:
                df = pd.read_csv(file_path, sep=sep, encoding=encoding, **kwargs)
                if len(df.columns) > 1:  # 成功分割出多列
                    return _process_dataframe_data(df, file_path, 'txt')
            except:
                continue
        
        # 如果分割失败，尝试读取为单列数据
        with open(file_path, 'r', encoding=encoding) as f:
            lines = f.readlines()
        
        # 提取数值
        values = []
        for line in lines:
            line = line.strip()
            if line and not line.startswith('#'):  # 跳过空行和注释
                try:
                    value = float(line)
                    values.append(value)
                except ValueError:
                    continue
        
        if values:
            return {
                'signal': np.array(values),
                'sampling_rate': 4.0,
                'metadata': {
                    'file_path': str(file_path),
                    'file_format': 'txt',
                    'data_type': 'single_column',
                    'total_lines': len(lines),
                    'valid_values': len(values),
                    'load_timestamp': datetime.now().isoformat()
                }
            }
        else:
            logger.error("TXT文件中未找到有效的数值数据")
            return None
            
    except Exception as e:
        logger.error(f"加载TXT文件失败: {str(e)}")
        return None

def _process_dataframe_data(df: pd.DataFrame, file_path: Path, file_format: str) -> Optional[Dict[str, Any]]:
    """处理DataFrame格式的数据"""
    # 检测数据列
    signal_column = None
    time_column = None
    
    # 常见的EDA信号列名
    eda_column_names = ['eda', 'gsr', 'conductance', 'signal', 'value', 'data']
    time_column_names = ['time', 'timestamp', 't', 'seconds']
    
    # 查找EDA信号列
    for col in df.columns:
        col_lower = str(col).lower()
        if any(name in col_lower for name in eda_column_names):
            signal_column = col
            break
    
    # 查找时间列
    for col in df.columns:
        col_lower = str(col).lower()
        if any(name in col_lower for name in time_column_names):
            time_column = col
            break
    
    # 如果没找到明确的信号列，使用第一个数值列
    if signal_column is None:
        numeric_columns = df.select_dtypes(include=[np.number]).columns
        if len(numeric_columns) > 0:
            signal_column = numeric_columns[0]
        else:
            logger.error(f"{file_format.upper()}文件中未找到数值列")
            return None
    
    # 提取信号数据
    eda_signal = df[signal_column].values
    
    # 计算采样率
    sampling_rate = 4.0  # 默认采样率
    if time_column is not None:
        time_data = df[time_column].values
        if len(time_data) > 1:
            time_diff = np.diff(time_data)
            avg_interval = np.mean(time_diff)
            if avg_interval > 0:
                sampling_rate = 1.0 / avg_interval
    
    # 构建结果
    result = {
        'signal': eda_signal,
        'sampling_rate': sampling_rate,
        'metadata': {
            'file_path': str(file_path),
            'file_format': file_format,
            'signal_column': str(signal_column),
            'time_column': str(time_column) if time_column is not None else None,
            'total_columns': len(df.columns),
            'data_shape': df.shape,
            'load_timestamp': datetime.now().isoformat()
        }
    }
    
    # 添加其他列作为附加数据
    additional_data = {}
    for col in df.columns:
        if col != signal_column and col != time_column:
            additional_data[str(col)] = df[col].values
    
    if additional_data:
        result['additional_data'] = additional_data
    
    return result

def detect_file_format(file_path: Union[str, Path]) -> str:
    """检测文件格式
    
    Args:
        file_path: 文件路径
        
    Returns:
        文件格式字符串 ('csv', 'json', 'excel', 'txt', 'unknown')
    """
    file_path = Path(file_path)
    suffix = file_path.suffix.lower()
    
    format_mapping = {
        '.csv': 'csv',
        '.json': 'json',
        '.xlsx': 'excel',
        '.xls': 'excel',
        '.txt': 'txt',
        '.dat': 'txt',
        '.tsv': 'txt'
    }
    
    return format_mapping.get(suffix, 'unknown')

def validate_eda_data(data: Union[Dict[str, Any], np.ndarray]) -> Optional[Dict[str, Any]]:
    """验证EDA数据格式和内容
    
    Args:
        data: EDA数据
        
    Returns:
        验证后的EDA数据字典
    """
    try:
        # 如果输入是numpy数组，转换为字典格式
        if isinstance(data, np.ndarray):
            data = {
                'signal': data,
                'sampling_rate': 4.0,
                'metadata': {
                    'data_type': 'numpy_array',
                    'validation_timestamp': datetime.now().isoformat()
                }
            }
        
        # 检查必需字段
        if not isinstance(data, dict):
            logger.error("EDA数据必须是字典格式")
            return None
        
        if 'signal' not in data:
            logger.error("EDA数据缺少'signal'字段")
            return None
        
        # 验证信号数据
        signal = data['signal']
        if not isinstance(signal, np.ndarray):
            signal = np.array(signal)
            data['signal'] = signal
        
        # 检查信号长度
        if len(signal) == 0:
            logger.error("EDA信号数据为空")
            return None
        
        if len(signal) < 10:
            logger.warning(f"EDA信号长度过短: {len(signal)} 个采样点")
        
        # 检查数据类型
        if not np.issubdtype(signal.dtype, np.number):
            logger.error("EDA信号必须是数值类型")
            return None
        
        # 检查无效值
        nan_count = np.sum(np.isnan(signal))
        inf_count = np.sum(np.isinf(signal))
        
        if nan_count > 0:
            logger.warning(f"EDA信号包含 {nan_count} 个NaN值")
        
        if inf_count > 0:
            logger.warning(f"EDA信号包含 {inf_count} 个无穷值")
        
        # 检查采样率
        sampling_rate = data.get('sampling_rate', 4.0)
        if not isinstance(sampling_rate, (int, float)) or sampling_rate <= 0:
            logger.warning("采样率无效，使用默认值4.0 Hz")
            data['sampling_rate'] = 4.0
        
        # 添加验证信息到元数据
        if 'metadata' not in data:
            data['metadata'] = {}
        
        data['metadata'].update({
            'validation_passed': True,
            'signal_length': len(signal),
            'signal_dtype': str(signal.dtype),
            'nan_count': int(nan_count),
            'inf_count': int(inf_count),
            'signal_range': [float(np.nanmin(signal)), float(np.nanmax(signal))],
            'validation_timestamp': datetime.now().isoformat()
        })
        
        logger.debug("EDA数据验证通过")
        return data
        
    except Exception as e:
        logger.error(f"EDA数据验证失败: {str(e)}")
        return None

def save_eda_results(results: Dict[str, Any], 
                    output_path: Union[str, Path],
                    format: str = 'json',
                    include_raw_signal: bool = False) -> bool:
    """保存EDA处理结果
    
    Args:
        results: EDA处理结果字典
        output_path: 输出文件路径
        format: 输出格式 ('json', 'csv', 'excel')
        include_raw_signal: 是否包含原始信号数据
        
    Returns:
        保存是否成功
    """
    try:
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 准备保存的数据
        save_data = results.copy()
        
        # 处理numpy数组
        save_data = _convert_numpy_arrays(save_data)
        
        # 是否包含原始信号
        if not include_raw_signal:
            if 'preprocessed_signal' in save_data:
                del save_data['preprocessed_signal']
            if 'raw_signal' in save_data:
                del save_data['raw_signal']
        
        # 根据格式保存
        if format.lower() == 'json':
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(save_data, f, indent=2, ensure_ascii=False)
        
        elif format.lower() == 'csv':
            # 将结果展平为CSV格式
            flat_data = _flatten_dict(save_data)
            df = pd.DataFrame([flat_data])
            df.to_csv(output_path, index=False, encoding='utf-8')
        
        elif format.lower() == 'excel':
            # 创建多个工作表
            with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
                # 主要指标
                if 'metrics' in save_data:
                    metrics_df = pd.DataFrame([save_data['metrics']])
                    metrics_df.to_excel(writer, sheet_name='指标', index=False)
                
                # 质量评估
                if 'quality_assessment' in save_data:
                    quality_df = pd.DataFrame([save_data['quality_assessment']])
                    quality_df.to_excel(writer, sheet_name='质量评估', index=False)
                
                # 峰值信息
                if 'peaks' in save_data and save_data['peaks']:
                    peaks_df = pd.DataFrame(save_data['peaks'])
                    peaks_df.to_excel(writer, sheet_name='峰值', index=False)
        
        else:
            logger.error(f"不支持的输出格式: {format}")
            return False
        
        logger.debug(f"EDA结果保存成功: {output_path}")
        return True
        
    except Exception as e:
        logger.error(f"保存EDA结果失败: {str(e)}")
        return False

def _convert_numpy_arrays(obj):
    """递归转换numpy数组为Python原生类型"""
    if isinstance(obj, np.ndarray):
        return obj.tolist()
    elif isinstance(obj, np.integer):
        return int(obj)
    elif isinstance(obj, np.floating):
        return float(obj)
    elif isinstance(obj, dict):
        return {key: _convert_numpy_arrays(value) for key, value in obj.items()}
    elif isinstance(obj, list):
        return [_convert_numpy_arrays(item) for item in obj]
    else:
        return obj

def _flatten_dict(d: Dict[str, Any], parent_key: str = '', sep: str = '_') -> Dict[str, Any]:
    """展平嵌套字典"""
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(_flatten_dict(v, new_key, sep=sep).items())
        elif isinstance(v, list) and len(v) > 0 and isinstance(v[0], dict):
            # 处理字典列表，只取第一个元素
            items.extend(_flatten_dict(v[0], new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

def export_to_matlab(results: Dict[str, Any], 
                    output_path: Union[str, Path]) -> bool:
    """导出结果到MATLAB格式
    
    Args:
        results: EDA处理结果
        output_path: 输出.mat文件路径
        
    Returns:
        导出是否成功
    """
    try:
        from scipy.io import savemat
        
        # 转换数据格式
        matlab_data = _convert_numpy_arrays(results)
        
        # 保存为.mat文件
        savemat(output_path, matlab_data)
        
        logger.debug(f"MATLAB格式导出成功: {output_path}")
        return True
        
    except ImportError:
        logger.error("需要安装scipy库才能导出MATLAB格式")
        return False
    except Exception as e:
        logger.error(f"导出MATLAB格式失败: {str(e)}")
        return False


class EDADataLoader:
    """EDA数据加载器类
    
    提供面向对象的EDA数据加载接口
    """
    
    def __init__(self, encoding: str = 'utf-8'):
        """初始化数据加载器
        
        Args:
            encoding: 默认文件编码
        """
        self.encoding = encoding
        self.logger = logging.getLogger(__name__)
    
    def load(self, file_path: Union[str, Path], **kwargs) -> Optional[Union[Dict[str, Any], np.ndarray]]:
        """加载EDA数据文件
        
        Args:
            file_path: 文件路径
            **kwargs: 额外的加载参数
            
        Returns:
            加载的EDA数据
        """
        return load_eda_data(file_path, encoding=self.encoding, **kwargs)
    
    def load_batch(self, file_paths: List[Union[str, Path]], **kwargs) -> Dict[str, Any]:
        """批量加载EDA数据文件
        
        Args:
            file_paths: 文件路径列表
            **kwargs: 额外的加载参数
            
        Returns:
            批量加载结果字典
        """
        results = {}
        for file_path in file_paths:
            file_path = Path(file_path)
            try:
                data = self.load(file_path, **kwargs)
                if data is not None:
                    results[file_path.name] = data
                else:
                    self.logger.warning(f"无法加载文件: {file_path}")
            except Exception as e:
                self.logger.error(f"加载文件失败 {file_path}: {str(e)}")
                
        return results


class EDAResultsSaver:
    """EDA结果保存器类
    
    提供面向对象的EDA结果保存接口
    """
    
    def __init__(self, default_format: str = 'json'):
        """初始化结果保存器
        
        Args:
            default_format: 默认保存格式
        """
        self.default_format = default_format
        self.logger = logging.getLogger(__name__)
    
    def save(self, results: Dict[str, Any], 
             output_path: Union[str, Path], 
             format: Optional[str] = None,
             **kwargs) -> bool:
        """保存EDA处理结果
        
        Args:
            results: EDA处理结果
            output_path: 输出文件路径
            format: 保存格式，如果为None则使用默认格式
            **kwargs: 额外的保存参数
            
        Returns:
            保存是否成功
        """
        if format is None:
            format = self.default_format
            
        return save_eda_results(results, output_path, format=format, **kwargs)
    
    def save_batch(self, results_dict: Dict[str, Dict[str, Any]], 
                   output_dir: Union[str, Path],
                   format: Optional[str] = None,
                   **kwargs) -> Dict[str, bool]:
        """批量保存EDA处理结果
        
        Args:
            results_dict: 批量结果字典，键为文件名，值为处理结果
            output_dir: 输出目录
            format: 保存格式
            **kwargs: 额外的保存参数
            
        Returns:
            保存状态字典
        """
        output_dir = Path(output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)
        
        save_status = {}
        for filename, results in results_dict.items():
            try:
                # 构建输出文件路径
                output_path = output_dir / f"{Path(filename).stem}_results.{format or self.default_format}"
                success = self.save(results, output_path, format=format, **kwargs)
                save_status[filename] = success
                
                if success:
                    self.logger.info(f"成功保存结果: {output_path}")
                else:
                    self.logger.warning(f"保存结果失败: {output_path}")
                    
            except Exception as e:
                self.logger.error(f"保存结果异常 {filename}: {str(e)}")
                save_status[filename] = False
                
        return save_status